/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.p2psimulator.entity.overlaynetwork;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.p2psimulator.engine.Environment;
import org.p2psimulator.entity.Id;
import org.p2psimulator.entity.Peer;
import org.p2psimulator.entity.overlaynetwork.routingtable.FloodRoutingTable;
import org.p2psimulator.entity.overlaynetwork.routingtable.RoutingTable;
import org.p2psimulator.entity.overlaynetwork.routingtable.SuperpeerRoutingTable;
import org.p2psimulator.exceptions.OverlayNetworkException;

/**
 *
 * @author amorim
 */
public class SuperpeerOverlay extends FloodOverlay {

    private Random r;
    private List<Id> superPeers;

    public SuperpeerOverlay(Environment env) {
        super(env);
        this.r = new Random();
        this.superPeers = new ArrayList<Id>();
    }

    @Override
    public void generateOverlay() throws OverlayNetworkException {

        int totalPeers = env.getGeneralConfig().getSimConfig().getTotalPeers();
        int totalSuperPeers = env.getGeneralConfig().getSimConfig().getTotalSuperPeers();

        int step = totalPeers / totalSuperPeers;
        int i = 0;
        for (Id id : env.getPeerBank().getPeerMap().keySet()) {
            int maxSize = env.getGeneralConfig().getSimConfig().getMaxPeerConnectivity();
            SuperpeerRoutingTable routingTable = new SuperpeerRoutingTable(maxSize);
            // create a superpeer
            if ((i % step) == 0) {
                routingTable.setSuperpeer(true);
                superPeers.add(id);
            } else {
                routingTable.setSuperpeer(false);
                onlinePeers.add(id);
            }
            overlayMap.put(id, routingTable);
            Peer p = env.getPeerBank().getPeerById(id);
            p.setOnline(true);
            i++;
        }
        for (Id speer : superPeers) {
            connectPeers(speer, env.getGeneralConfig().getSimConfig().getMaxPeerConnectivity());
        }
       for (Id id : env.getPeerBank().getPeerMap().keySet()) {
            SuperpeerRoutingTable routingTable = (SuperpeerRoutingTable) getRoutingTable(id);
            if(!routingTable.isSuperpeer())
                joinNetwork(id);
        }

    }

    @Override
    public void leaveNetwork(Id idPeer) throws OverlayNetworkException {

        if (!this.overlayMap.containsKey(idPeer)) {
            return;
        }
        SuperpeerRoutingTable routingTable = (SuperpeerRoutingTable) getRoutingTable(idPeer);
        if (!routingTable.isSuperpeer()) {
            // take peer from online vector
            if (!this.offlinePeers.contains(idPeer)) {
                this.offlinePeers.add(idPeer);
            }
            // put peer into offline vector
            if (this.onlinePeers.contains(idPeer)) {
                this.onlinePeers.remove(idPeer);
            }
        }
        // set Peer offline
        Peer p = env.getPeerBank().getPeerById(idPeer);
        p.setOnline(false);
    }

    @Override
    public void joinNetwork(Id idPeer) throws OverlayNetworkException {

        if (!this.overlayMap.containsKey(idPeer)) {
            return;
        }

        SuperpeerRoutingTable routingTable = (SuperpeerRoutingTable) getRoutingTable(idPeer);
        
        if (!routingTable.isSuperpeer()) {
            Id superpeer = findRandomSuperpeer();
            if (superpeer == null) {
                return;
            }
            routingTable.clear();
            routingTable.insertNeighbor(superpeer);

            SuperpeerRoutingTable superRoutingTable = (SuperpeerRoutingTable) getRoutingTable(superpeer);
            superRoutingTable.addLeaf(idPeer);

            List<Id> files = env.getFileSystem().getPeerFiles(idPeer);
            for (Id id : files) {
                superRoutingTable.addFileId(idPeer, id);
            }
            // take peer from online vector
            if (!this.offlinePeers.contains(idPeer)) {
                this.offlinePeers.remove(idPeer);
            }
            // put peer into offline vector
            if (this.onlinePeers.contains(idPeer)) {
                this.onlinePeers.add(idPeer);
            }
        } else {
            connectPeers(idPeer, env.getGeneralConfig().getSimConfig().getMaxPeerConnectivity());
        }

        // set Peer online
        Peer p = env.getPeerBank().getPeerById(idPeer);
        p.setOnline(true);
    }

    @Override
    public RoutingTable getRoutingTable(Id idPeer) throws OverlayNetworkException {
        if (this.overlayMap.containsKey(idPeer)) {
            return this.overlayMap.get(idPeer);
        } else {
            return null;
        }
    }

    @Override
    public String printOverlay() {
        StringBuilder ret = new StringBuilder();
        for (Id id : overlayMap.keySet()) {
            ret.append("\n PEER [" + id + "] ::= ");
            SuperpeerRoutingTable routingTable = (SuperpeerRoutingTable) overlayMap.get(id);
            for (Id neigh : routingTable.getNeighbors()) {
                ret.append(" [" + neigh + "] ");
            }
            ret.append(" ==>> ");
            for (Id neigh : routingTable.getLeavesMap().keySet()) {
                ret.append(" [" + neigh + "] ");
            }
        }
        return ret.toString();
    }

    public Id findRandomSuperpeer() {
        int k = r.nextInt(superPeers.size());
        int j = 0;
        for (Id id : superPeers) {
            j++;
            if (j >= k) {
                return id;
            }
        }
        return superPeers.get(0);
    }

    /**
     * Adiciona um peer b a lista de vizinhos do peer a
     *
     * @param a
     * @param b
     */
    public void associatePeers(Id a, Id b) {

        synchronized (overlayMap) {

            if (a == null || b == null) {
                return;
            }
            overlayMap.get(a).insertNeighbor(b);
        }
    }

    @Override
    public FloodRoutingTable getPeerNeighbors(Id id) {

        if (!this.overlayMap.containsKey(id)) {
            return null;
        } else {

            return overlayMap.get(id);
        }
    }

    @Override
    public List<Id> getUncompletePeerList(int maxNeighbors, Id except) {
        List<Id> vector = new ArrayList<Id>();
        Random r = new Random();
        for (Id id : superPeers) {
            if (overlayMap.get(id).size() < maxNeighbors) {
                if (!id.isEqual(except)) {
                    int idx = vector.size() > 0 ? r.nextInt(vector.size())
                            : 0;
                    vector.add(idx, id);
                }
            }
        }
        return vector;
    }

    @Override
    public List<Id> getOnlineUncompletePeerList(int maxNeighbors, Id except) {
        List<Id> vector = new ArrayList<Id>();
        Random r = new Random();
        for (Id id : superPeers) {
            if (onlinePeers.contains(id)) {
                if (overlayMap.get(id).size() < maxNeighbors) {
                    if (!id.isEqual(except)) {
                        int idx = vector.size() > 0 ? r.nextInt(vector.size())
                                : 0;
                        vector.add(idx, id);
                    }

                }
            }
        }
        return vector;
    }
}
